﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Media.Animation;
using System.Collections.ObjectModel;
using System.Collections;
using System.ComponentModel;
using UmlControlLibrary;

namespace UmlControlLibrary
{
    /// <summary>
    /// Interaction logic for UmlClass.xaml
    /// </summary>
   public partial class UmlDesignerElement : SelectableItemControl, IDraggable, INotifyPropertyChanged
    {
        public static DependencyProperty GradientStartProperty = DependencyProperty.Register("GradientStart", typeof(string), typeof(UmlDesignerElement), new PropertyMetadata("#FFFFFCEB"));
        public static DependencyProperty GradientEndProperty = DependencyProperty.Register("GradientEnd", typeof(string), typeof(UmlDesignerElement), new PropertyMetadata("#FFFFF3AF"));
        public static DependencyProperty SectionsProperty = DependencyProperty.Register("Sections", typeof(ObservableCollection<UmlDesignerElementSection>), typeof(UmlDesignerElement));

        public static double ActionDecoratorMinOpacity = 0.25;

        private SelectableItemControl templatedElement = null;

        public UmlDesignerElement():base()
        {
            InitializeComponent();
            DataContextChanged += new DependencyPropertyChangedEventHandler(UmlDesignerElement_DataContextChanged);

            AddDragZone(NameSection);
            AddDragZone(Stereotype);
            AddDragZone(ParentLabel);           
            this.ContextMenu = new UmlDesignerElementContextMenu();
            this.ContextMenuOpening += new ContextMenuEventHandler(ItemsControl_ContextMenuOpening);
            Sections = new ObservableCollection<UmlDesignerElementSection>();
            AddSections();
            PropertyGroupingHelper.GetInstance().GroupingFuncChanged += new PropertyGroupingHelper.GroupingFuncChangedHandler(UmlDesignerElement_GroupingFuncChanged);
        }

        void UmlDesignerElement_GroupingFuncChanged()
        {
            foreach (UmlDesignerElementSection section in Sections)
                section.Refresh();
        }

        void UmlDesignerElement_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            foreach (UmlDesignerElementSection section in Sections)
                (section.Elements as Binding).Source = this.DataContext;
        }

        public string GradientStart
        {
            get { return (string)GetValue(UmlDesignerElement.GradientStartProperty); }
            set { SetValue(UmlDesignerElement.GradientStartProperty, value); }
        }

        public string GradientEnd
        {
            get { return (string)GetValue(UmlDesignerElement.GradientEndProperty); }
            set { SetValue(UmlDesignerElement.GradientEndProperty, value); }
        }

        public ObservableCollection<UmlDesignerElementSection> Sections
        {
            get { return (ObservableCollection<UmlDesignerElementSection>)GetValue(UmlDesignerElement.SectionsProperty); }
            set { SetValue(UmlDesignerElement.SectionsProperty, value); }
        }

        private void EnsureTemplatedElement()
        {
            if (templatedElement == null)
                templatedElement = (SelectableItemControl)GetTemplatedElement();
            OwnerElement = templatedElement.OwnerElement;
        }

        #region SelectableItemControl overrides

        public override Control GetTemplatedElement()
        {
            object o;
            o = TemplatedParent;
            while (!(o is SelectableItemControl))
            {
                o = (o as Control).TemplatedParent;
            }
            return o as SelectableItemControl;
        }

        public override void Select()
        {
            EnsureTemplatedElement();
            base.Select();
            // this = this control
            // parent = draggable control
            Control parent = this.TemplatedParent as Control;
            // c2 = draggable designer item
            Control parentContainer = parent.TemplatedParent as Control;
            // container = canvas
            Panel rootContainer = parentContainer.Parent as Panel;
            rootContainer.Children.Remove(parentContainer);
            rootContainer.Children.Add(parentContainer);
            Control resizer = (Control)((this.TemplatedParent as Control).FindName("resizer"));
            resizer.Visibility = Visibility.Visible;
            this.ShowActionsDecorator(true);
        }

        public override void Deselect()
        {
            base.Deselect();
            Control resizer = (Control)((this.TemplatedParent as Control).FindName("resizer"));
            resizer.Visibility = Visibility.Hidden;
            SetValue(Grid.ZIndexProperty, 0);
            this.ShowActionsDecorator(false);
        }

        public override void Resized()
        {
            Control tp = (this.TemplatedParent as Control);
            if (tp != null)
            {
                SelectableItemControl parent = tp.TemplatedParent as SelectableItemControl;
                if (parent != null)
                    parent.Resized();
            }
        }

        public Grid GetRootGrid()
        {
            UmlControl element = ((this.TemplatedParent as FrameworkElement).TemplatedParent as UmlControl);
            Grid rootGrid = (Grid)element.Template.FindName("UmlControlRootGrid", element);
            return rootGrid;
        }

        #endregion

        #region Actions Decorators


        protected Control GetExistingActionsDecorator()
        {
            Grid rootGrid = GetRootGrid();
            Control c;
            for (int i = 0; i < rootGrid.Children.Count; i++)
            {
                if ((rootGrid.Children[i] as FrameworkElement).Name == "ActionsDecorator")
                {
                    c = rootGrid.Children[i] as Control;
                    rootGrid.Children.Remove(c);
                    rootGrid.Children.Add(c);
                    return c;
                }
            }
            return null;
        }

        protected ControlTemplate GetActionsDecoratorTemplate()
        {
            ControlTemplate t = new ControlTemplate(typeof(Control));
            FrameworkElementFactory grid = new FrameworkElementFactory(typeof(Grid));
            //grid.SetValue(Control.OpacityProperty, 0.25);
            t.VisualTree = grid;

            FrameworkElementFactory btnGrid = new FrameworkElementFactory(typeof(Grid));
            btnGrid.Name = "btnTop";
            btnGrid.SetValue(Control.HeightProperty, 23.0);
            btnGrid.SetValue(Control.WidthProperty, 23.0);
            Thickness thickness = new Thickness(0, -23, 0, 0);
            btnGrid.SetValue(Control.MarginProperty, thickness);
            btnGrid.SetValue(Control.VerticalAlignmentProperty, VerticalAlignment.Top);
            btnGrid.SetValue(Control.HorizontalAlignmentProperty, HorizontalAlignment.Center);
            FrameworkElementFactory panel = new FrameworkElementFactory(typeof(StackPanel));
            panel.SetValue(StackPanel.HorizontalAlignmentProperty, HorizontalAlignment.Center);
            panel.SetValue(StackPanel.OrientationProperty, Orientation.Horizontal);
            btnGrid.AppendChild(panel);
            grid.AppendChild(btnGrid);
            actionDecoratorMargins.Add(btnGrid, thickness);

            btnGrid = new FrameworkElementFactory(typeof(Grid));
            btnGrid.Name = "btnBottom";
            btnGrid.SetValue(Control.HeightProperty, 23.0);
            btnGrid.SetValue(Control.WidthProperty, 23.0);
            thickness = new Thickness(0, 0, 0, -23);
            btnGrid.SetValue(Control.MarginProperty, thickness);
            btnGrid.SetValue(Control.VerticalAlignmentProperty, VerticalAlignment.Bottom);
            btnGrid.SetValue(Control.HorizontalAlignmentProperty, HorizontalAlignment.Center);
            panel = new FrameworkElementFactory(typeof(StackPanel));
            panel.SetValue(StackPanel.HorizontalAlignmentProperty, HorizontalAlignment.Center);
            panel.SetValue(StackPanel.OrientationProperty, Orientation.Horizontal);
            btnGrid.AppendChild(panel);
            grid.AppendChild(btnGrid);
            actionDecoratorMargins.Add(btnGrid, thickness);

            btnGrid = new FrameworkElementFactory(typeof(Grid));
            btnGrid.Name = "btnLeft";
            btnGrid.SetValue(Control.HeightProperty, 23.0);
            btnGrid.SetValue(Control.WidthProperty, 23.0);
            thickness = new Thickness(-23, 0, 0, 0);
            btnGrid.SetValue(Control.MarginProperty, thickness);
            btnGrid.SetValue(Control.VerticalAlignmentProperty, VerticalAlignment.Center);
            btnGrid.SetValue(Control.HorizontalAlignmentProperty, HorizontalAlignment.Left);
            panel = new FrameworkElementFactory(typeof(StackPanel));
            panel.SetValue(StackPanel.HorizontalAlignmentProperty, HorizontalAlignment.Right);
            panel.SetValue(StackPanel.OrientationProperty, Orientation.Horizontal);
            panel.SetValue(StackPanel.FlowDirectionProperty, FlowDirection.RightToLeft);
            btnGrid.AppendChild(panel);
            grid.AppendChild(btnGrid);
            actionDecoratorMargins.Add(btnGrid, thickness);

            btnGrid = new FrameworkElementFactory(typeof(Grid));
            btnGrid.Name = "btnRight";
            btnGrid.SetValue(Control.HeightProperty, 23.0);
            btnGrid.SetValue(Control.WidthProperty, 23.0);
            thickness = new Thickness(0, 0, -23, 0);
            btnGrid.SetValue(Control.MarginProperty, thickness);
            btnGrid.SetValue(Control.VerticalAlignmentProperty, VerticalAlignment.Center);
            btnGrid.SetValue(Control.HorizontalAlignmentProperty, HorizontalAlignment.Right);
            panel = new FrameworkElementFactory(typeof(StackPanel));
            panel.SetValue(StackPanel.OrientationProperty, Orientation.Horizontal);
            btnGrid.AppendChild(panel);
            grid.AppendChild(btnGrid);
            actionDecoratorMargins.Add(btnGrid, thickness);

            Grid rootGrid = GetRootGrid();

            Control c = new Control();
            // c.Template = t;
            // c.ApplyTemplate();

            c.Name = "ActionsDecorator";

            rootGrid.Children.Add(c);
            c.Visibility = Visibility.Hidden;
            c.Opacity = ActionDecoratorMinOpacity;

            rootGrid.MouseEnter += new MouseEventHandler(c_MouseEnter);
            rootGrid.MouseLeave += new MouseEventHandler(c_MouseLeave);

            return t;
        }

        private Dictionary<FrameworkElementFactory, Thickness> actionDecoratorMargins = new Dictionary<FrameworkElementFactory, Thickness>();

        protected FrameworkElementFactory RegisterActionDecorator(ControlTemplate t, string place, string imageName, MouseButtonEventHandler handler, string tooltip)
        {
            FrameworkElementFactory btnGrid = t.VisualTree.FirstChild;
            while ((btnGrid != null) && (btnGrid.Name != place))
                btnGrid = btnGrid.NextSibling;

            if (btnGrid != null)
            {
                FrameworkElementFactory panel = btnGrid.FirstChild;
                FrameworkElementFactory border = new FrameworkElementFactory(typeof(Border));
                border.SetValue(Border.BackgroundProperty, Brushes.Beige);
                border.SetValue(Border.HeightProperty, 23.0);
                border.SetValue(Border.WidthProperty, 23.0);
                border.SetValue(Border.BorderThicknessProperty, new Thickness(1.0));
                border.SetValue(Border.BorderBrushProperty, Brushes.Black);
                border.SetValue(Border.ToolTipProperty, tooltip);
                border.AddHandler(Border.PreviewMouseDownEvent, handler);

                panel.AppendChild(border);

                FrameworkElementFactory btnImage = new FrameworkElementFactory(typeof(Image));
                btnImage.SetValue(Image.SourceProperty, new BitmapImage(new Uri("pack://application:,,,/UmlControlLibrary;component/Resources/Images/" + imageName)));
                btnImage.SetValue(Image.StretchProperty, Stretch.None);
                btnImage.SetValue(Image.VerticalAlignmentProperty, VerticalAlignment.Center);
                btnImage.SetValue(Image.HorizontalAlignmentProperty, HorizontalAlignment.Center);
                border.AppendChild(btnImage);

                Thickness thickness = actionDecoratorMargins[btnGrid];

                int childCount = 0;
                FrameworkElementFactory child = panel.FirstChild;
                while (child != null)
                {
                    childCount++;
                    child = child.NextSibling;
                }
                btnGrid.SetValue(Grid.WidthProperty, 23.0 * childCount);
                thickness.Left = Math.Sign(thickness.Left) * 23.0 * childCount;
                thickness.Right = Math.Sign(thickness.Right) * 23.0 * childCount;
                btnGrid.SetValue(Grid.MarginProperty, thickness);

                return border;
            }

            return null;
        }

        void c_MouseLeave(object sender, MouseEventArgs e)
        {
            base.OnMouseLeave(e);
            ShowActionsDecoratorAnimate(false);

        }

        void c_MouseEnter(object sender, MouseEventArgs e)
        {
            base.OnMouseEnter(e);
            ShowActionsDecoratorAnimate(true);
        }

        private void ShowActionsDecoratorAnimate(bool show)
        {
            Control actionsDecorator = GetControlActionsDecorator();
            if (actionsDecorator != null)
            {
                DoubleAnimation dblAnimation = new DoubleAnimation();
                dblAnimation.From = actionsDecorator.Opacity;
                if (show)
                {
                    dblAnimation.To = 1.0;
                    dblAnimation.Duration = new Duration(TimeSpan.FromSeconds(0.1));
                }
                else
                {
                    dblAnimation.To = ActionDecoratorMinOpacity;
                    dblAnimation.Duration = new Duration(TimeSpan.FromSeconds(0.45));
                }
                actionsDecorator.BeginAnimation(Control.OpacityProperty, dblAnimation);
            }
        }


        private void ShowActionsDecorator(bool visible)
        {
            Control actionsDecorator = GetControlActionsDecorator();
            if (actionsDecorator != null)
            {
                if (visible)
                    actionsDecorator.Visibility = Visibility.Visible;
                else
                    actionsDecorator.Visibility = Visibility.Hidden;
                ShowActionsDecoratorAnimate(visible);
            }
        }

        protected virtual Control GetControlActionsDecorator()
        {
            return GetExistingActionsDecorator();
        }
        
        #endregion

        #region Class section animations

        void expMenu_Collapsed(object sender, RoutedEventArgs e)
        {
            Select();
            DoubleAnimation dblAnimation = new DoubleAnimation();
            //set the width that we want the expander control to
            //have when animation is complete
            double contentSize = ((sender as Expander).Content as ItemsControl).ActualHeight;
            double currentSize = (sender as Expander).ActualHeight;
            dblAnimation.From = currentSize;
            dblAnimation.To = currentSize - contentSize;
            //set the duration of this animation - generally 1/4 or less looks best
            dblAnimation.Duration = new Duration(TimeSpan.FromSeconds(.10));
            //begin the animation on the width property of the expMenu control
            (sender as Expander).BeginAnimation(Expander.HeightProperty, dblAnimation);
        }

        void expMenu_Expanded(object sender, RoutedEventArgs e)
        {
            Select();
            DoubleAnimationPlus dblAnimation = new DoubleAnimationPlus();
            //set the width that we want the expander control to
            //have when animation is complete
            double contentSize = ((sender as Expander).Content as ItemsControl).ActualHeight;
            double currentSize = (sender as Expander).ActualHeight;
            dblAnimation.TargetElement = sender as UIElement;
            dblAnimation.FillBehavior = FillBehavior.Stop;
            dblAnimation.From = currentSize;
            dblAnimation.To = currentSize + contentSize;
            //set the duration of this animation - generally 1/4 or less looks best
            dblAnimation.Duration = new Duration(TimeSpan.FromSeconds(.25));
            dblAnimation.Completed += new EventHandler(dblAnimation_Completed);
            //AnimationEventSender.Add(dblAnimation, sender as Expander);
            //begin the animation on the Height property of the expMenu control
            DoubleAnimationPlus tr = new DoubleAnimationPlus();
            tr.TargetElement = sender as UIElement;
            tr.From = 0;
            tr.To = 1;
            tr.Duration = dblAnimation.Duration;

            (sender as Expander).BeginAnimation(Expander.HeightProperty, dblAnimation);
            ((sender as Expander).Content as ItemsControl).BeginAnimation(ItemsControl.OpacityProperty, tr);
        }

        void dblAnimation_Completed(object sender, EventArgs e)
        {
            Expander expander = ((sender as Clock).Timeline as DoubleAnimationPlus).TargetElement as Expander;
            expander.Height = double.NaN;
        }

        #endregion

        #region IDraggable Members

        public void Drag(double deltaX, double deltaY)
        {
            double x = Canvas.GetLeft(templatedElement)+deltaX;
            double y = Canvas.GetTop(templatedElement)+deltaY;

            x = Math.Max(Math.Min(x, (this.templatedElement.Parent as FrameworkElement).ActualWidth - templatedElement.ActualWidth), 0);
            y = Math.Max(Math.Min(y, (this.templatedElement.Parent as FrameworkElement).ActualHeight - templatedElement.ActualHeight), 0);

            double left = AlignToGrid(x);
            //double left = AlignToGrid(x + deltaX);
            double top = AlignToGrid(y);
            //double top = AlignToGrid(y + deltaY);
            Canvas.SetLeft(templatedElement, left);
            Canvas.SetTop(templatedElement, top);
            SelectableItemControl parent = (this.TemplatedParent as Control).TemplatedParent as SelectableItemControl;
            if (parent is IDraggable)
                (parent as IDraggable).Drag(deltaX, deltaY);
            this.Moved();
        }

        #endregion

        private void UmlClassData_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            this.Resized();
        }

        public void AddSection(string SectionName, string SectionHeader, string ElementsPath, SpecificMenu menu)
        {
            UmlDesignerElementSection section = new UmlDesignerElementSection(SectionName, SectionHeader, ElementsPath, menu);
            Sections.Add(section);
            OnPropertyChanged("Sections");
        }

        public virtual void AddSections()
        {
            Sections.Clear();
        }

        public void SetGradientStart(string color)
        {
            GradientStart = color;
        }

        public void SetGradientEnd(string color)
        {
            GradientEnd = color;
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        public void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion

        private void ItemsControl_Initialized(object sender, EventArgs e)
        {
            ItemsControl itemsControl = sender as ItemsControl;
            itemsControl.SetBinding(ItemsControl.ItemsSourceProperty, (itemsControl.DataContext as UmlDesignerElementSection).Elements);
            NameLabel.AfterEdit += new RoutedEventHandler(NameLabel_AfterEdit);
            NameLabel.BeforeEdit += new RoutedEventHandler(NameLabel_BeforeEdit);
        }

        private void ItemsControl_ContextMenuOpening(object sender, ContextMenuEventArgs e)
        {
            UIElement element = e.OriginalSource as UIElement;

            if (e.OriginalSource is FrameworkContentElement)
                element = (e.OriginalSource as FrameworkContentElement).Parent as UIElement;

            FrameworkElement source = (element as FrameworkElement);
            if (source != null)
                while (source.TemplatedParent != null)
                    source = source.TemplatedParent as FrameworkElement;
            if (source != null)
                (this.ContextMenu as UmlDesignerElementContextMenu).SetMenuContexts(e, this.DataContext, source.DataContext);
        }

        private void Expander_MouseDown(object sender, MouseButtonEventArgs e)
        {

        }

        private void NameLabel_BeforeEdit(object sender, RoutedEventArgs e)
        {
            Binding binding = new Binding("MyName");
            binding.Mode = BindingMode.TwoWay;
            binding.Source = (sender as DraggableLabel).DataContext;
            (sender as DraggableLabel).SetBinding(DraggableLabel.TextProperty, binding);
        }

        private void NameLabel_AfterEdit(object sender, RoutedEventArgs e)
        {
            Binding binding = new Binding("Name");
            binding.Mode = BindingMode.TwoWay;
            binding.Source = (sender as DraggableLabel).DataContext;
            (sender as DraggableLabel).SetBinding(DraggableLabel.TextProperty, binding);
        }
    }


    public class PropertyTemplateSelector : DataTemplateSelector
    {
        public DataTemplate SectionHeaderTemplate
        {
            get;
            set;
        }

        public DataTemplate PropertyTemplate
        {
            get;
            set;
        }

        public override DataTemplate SelectTemplate(object item, DependencyObject container)
        {
            if (item is String)
                return SectionHeaderTemplate;
            else if (item is UmlMemberElement)
                return PropertyTemplate;
            else
                return base.SelectTemplate(item, container);
        }
    }
}
